--[[ 
A simple module containing partial functions (functions associated with a list of arguments), 
multicall functions (a callable table which calls multiple functions), and the super() function 
that can be used by methods to call another method (or itself) on all base classes of a given 
class.
--]]
module(..., package.seeall)
_G.func = _M


--------------------------------------------------------------------------------
-- partial - partial function application
--------------------------------------------------------------------------------
partial = class("partial")  -- acessible through 'func.partial'

function partial.__init(self, callable, ...)
    self.callable = callable
    self.args = {...}
end

function partial.__call(self, ...)
    local final_args = self.args
    if select("#", ...) > 0 then
        final_args = table.copy(self.args)
        for _, arg in ipairs{...} do
            table.insert(final_args, arg)
        end
    end
    return self.callable(table.unpack(final_args))
end

--------------------------------------------------------------------------------
-- multi - calls on a list of functions with the same arguments
--------------------------------------------------------------------------------
multi = class("multicall")  -- accessible through 'func.multi'

function multi.__init(self, funcs)
	for _, func in ipairs(funcs) do
    	table.insert(self, func)
	end
end

function multi.__call(self, ...)
    local all_results = {}
    for _, func in ipairs(self) do
        local func_results = { func(...) }
        table.insert(all_results, func_results)
    end
    return all_results
end

--------------------------------------------------------------------------------
-- class.super - calls methods on all base classes of a given class
--------------------------------------------------------------------------------

--[[ 
Unlike Python's super() function, which simply calls the requested method on the next element in 
the class' mro list (hence all classes *must* use super() for it to work properly, which is lame 
and error-prone), this function really does what it promises to do: it calls the requested method 
on all base classes of a given class. Note that this leads to a depth-first traversal of the 
hierarchy graph (not necessarily a tree), which causes multiple calls to some methods on classes 
that are base to more than one of the classes in the graph. However, I believe this is more 
predictable than Python's super() and much less error-prone (maybe this is just a matter of 
opinion, but a lot of people seem to agree with me that Python's super() is dangerous :P).
--]]
class.super = class("supercall")

function class.super.__init(self, cls, obj)
    self.cls = cls
    self.obj = obj or cls
    if obj ~= nil then
        if isinstance(obj, class) and not issubclass(obj, cls) then
            error("arg#2 is not a subclass of arg#1", 2)
        elseif not isinstance(obj, cls) then
            error("arg#2 is not an instance of arg#1", 2)
        end
    end
end

function class.super.__index(self, key)
    local methods = {}
    for _, base in ipairs(self.cls.__bases) do
        local method = rawget(base, self.name)
        if method ~= nil then table.insert(methods, method) end
    end
    return partial(multi(methods), self.obj)
end
